#include "PacketQueue.h"

PacketQueue::PacketQueue() :
    queue(nullptr),
    pktNum(0),
    size(0),
    duration(0),
    isAbort(true),
    serial(0)
{
}

PacketQueue::~PacketQueue()
{

}

int PacketQueue::packet_queue_init()
{
    // 创建环形缓冲区
    queue = av_fifo_alloc(sizeof(MyAVPacketList));
    if (not queue)
        return AVERROR(ENOMEM);
    return 0;
}

void PacketQueue::packet_queue_destroy()
{
    packet_queue_flush();
    av_fifo_freep(&queue);
}

void PacketQueue::packet_queue_flush()
{
    MyAVPacketList node;
    lock_guard<mutex> lock(_mutex);
    while ((uint64_t)av_fifo_size(queue) >= sizeof(node)) {
        av_fifo_generic_read(queue, &node, sizeof(node), nullptr);
        av_packet_free(&node.pkt);
    }
    pktNum = 0;
    size = 0;
    duration = 0;
    serial++;
}

void PacketQueue::packet_queue_start()
{
    lock_guard<mutex> lock(_mutex);
    isAbort = false;
    serial++;
}

void PacketQueue::packet_queue_abort()
{
    lock_guard<mutex> lock(_mutex);
    isAbort = true;             // 请求退出
    _condition.notify_all();    // 唤醒退出
}

int PacketQueue::packet_queue_put(AVPacket *pkt)
{
    AVPacket *pkt1;
    int ret;

    // 分配一个packet
    pkt1 = av_packet_alloc();
    if (!pkt1) {
        // 分配一个释放媒体包
        av_packet_unref(pkt);
        return -1;
    }
    // 转移内存到pkt1 并重新初始化pkt
    av_packet_move_ref(pkt1, pkt);

    {
        lock_guard<mutex> lock(_mutex);
        ret = packet_queue_put_private(pkt1); // 主要实现
    }

    //放入失败，才释放AVPacket
    if (ret < 0)
        av_packet_free(&pkt1);

    return ret;
}

int PacketQueue::packet_queue_put_private(AVPacket *pkt)
{
    // 节点
    MyAVPacketList node;

    // 如果已中止，则放入失败
    if (isAbort)
       return -1;

    if ((uint64_t)av_fifo_space(queue) < sizeof(node)) { // 判断队列空间是否足够
        if (av_fifo_grow(queue, sizeof(node)) < 0)   // 不足队列扩容
            return -1;  // 扩容失败
    }

    node.pkt = pkt;         // 获取指针
    node.serial = serial;   // 用队列序列号标记节点

    // 将节点插入队列
    av_fifo_generic_write(queue, &node, sizeof(node), nullptr);

    // 队列属性操作：增加节点数、cache大小、cache总时长, 用来控制队列的大小
    pktNum++;
    size += node.pkt->size + sizeof(node);
    duration += node.pkt->duration;

    // 发出信号，表明当前队列中有数据了，通知等待中的读线程可以取数据了
    _condition.notify_all();
    return 0;
}

int PacketQueue::packet_queue_put_nullpacket(AVPacket *pkt, int streamIndex)
{
    pkt->stream_index = streamIndex;
    return packet_queue_put(pkt);
}

int PacketQueue::packet_queue_get(AVPacket *pkt, int block, uint32_t *serial)
{
    MyAVPacketList node;
    int ret;

    unique_lock<mutex> lock(_mutex);

    for (;;) {
        if (isAbort) {    // 如果已中止，则取失败直接退出
            ret = -1;
            break;
        }

        if ((uint64_t)av_fifo_size(queue) >= sizeof(node)) { // 队列中至少有一个节点
            // 读出一个节点
            av_fifo_generic_read(queue, &node, sizeof(node), nullptr);
            //节点数减1
            pktNum--;
            // cache大小扣除一个节点
            size -= node.pkt->size + sizeof(node);
            // 总时长扣除一个节点
            duration -= node.pkt->duration;
            // 将节点中数据转移内存到pkt
            av_packet_move_ref(pkt, node.pkt);
            //如果需要输出serial，把serial输出
            if (serial)
                *serial = node.serial;
            // 释放资源 -> packet_queue_put的时候使用了av_packet_alloc
            av_packet_free(&node.pkt);
            ret = 1; // 成功
            break;
        } else if (!block) { //队列中没有数据，且非阻塞调用
            ret = 0;
            break;
        } else { //队列中没有数据，且阻塞调用
            //这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
            _condition.wait(lock);
        }
    }

    return ret;
}
